package org.magneato.utils;

import java.io.Serializable;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.ehcache.Element;
import net.sf.ehcache.constructs.blocking.LockTimeoutException;
import net.sf.ehcache.constructs.web.Header;
import net.sf.ehcache.constructs.web.PageInfo;
import net.sf.ehcache.constructs.web.filter.FilterNonReentrantException;
import net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SimplePageNoCachingFilter extends SimplePageCachingFilter {
	private static final Logger LOG = LoggerFactory
			.getLogger(SimplePageNoCachingFilter.class);

	private final VisitLog visitLog = new VisitLog();

	/**
	 * Build page info either using the cache or building the page directly.
	 * <p/>
	 * Some requests are for page fragments which should never be gzipped, or
	 * for other pages which are not gzipped.
	 */
	protected PageInfo buildPageInfo(final HttpServletRequest request,
			final HttpServletResponse response, final FilterChain chain)
			throws Exception {
		// Look up the cached page
		final String key = calculateKey(request);
		PageInfo pageInfo = null;
		try {
			checkNoReentry(request);
			LOG.debug("Getting page " + key + " status " + blockingCache.isValueInCache(key));
			LOG.debug("is value in cache status " + blockingCache.isValueInCache(key));
			Element element = blockingCache.get(key);
			LOG.debug("Element " + element);
			if (element == null || element.getObjectValue() == null) {
				boolean cache = true;
				try {
					// Page is not cached - build the response, cache it, and
					// send to client
					pageInfo = buildPage(request, response, chain);

					/*for (final Header<? extends Serializable> header : pageInfo
							.getHeaders()) {
						if (header.getName().equals("Cache-Control")) {
							if (header.getValue().equals("no-cache")) {
								LOG.debug("Don't cache this page" + blockingCache.isReadLockedByCurrentThread(key));
								cache = false; // don't cache this page
								
							}
						}
					}*/

					if (cache) {
						if (pageInfo.isOk()) { // 200
							if (LOG.isDebugEnabled()) {
								LOG.debug("PageInfo ok. Adding to cache "
										+ blockingCache.getName()
										+ " with key " + key);
							}
							blockingCache.put(new Element(key, pageInfo));
						} else { // cache Null response
							if (LOG.isDebugEnabled()) {
								LOG.debug("PageInfo was not ok(200). Putting null into cache "
										+ blockingCache.getName()
										+ " with key " + key);
							}
							blockingCache.put(new Element(key, null));
						}
					}
				} catch (final Throwable throwable) {
					// Must unlock the cache if the above fails. Will be logged
					// at Filter
					if (cache) {
						LOG.debug("Unlocking cache with key " + key);
						blockingCache.put(new Element(key, null));
					}
					throw new Exception(throwable);
				}
			} else {
				pageInfo = (PageInfo) element.getObjectValue();
			}
		} catch (LockTimeoutException e) {
			// do not release the lock, because you never acquired it
			throw e;
		} finally {
			// all done building page, reset the re-entrant flag
			visitLog.clear();
		}
		return pageInfo;
	}

	/**
	 * Check that this caching filter is not being reentered by the same
	 * recursively. Recursive calls will block indefinitely because the first
	 * request has not yet unblocked the cache.
	 * <p/>
	 * This condition usually indicates an error in filter chaining or
	 * RequestDispatcher dispatching.
	 * 
	 * @param httpRequest
	 * @throws FilterNonReentrantException
	 *             if reentry is detected
	 */
	protected void checkNoReentry(final HttpServletRequest httpRequest)
			throws FilterNonReentrantException {
		String filterName = getClass().getName();
		if (visitLog.hasVisited()) {
			throw new FilterNonReentrantException(
					"The request thread is attempting to reenter" + " filter "
							+ filterName + ". URL: "
							+ httpRequest.getRequestURL());
		} else {
			// mark this thread as already visited
			visitLog.markAsVisited();
			if (LOG.isDebugEnabled()) {
				LOG.debug("Thread {}  has been marked as visited.", Thread
						.currentThread().getName());
			}
		}
	}

	/**
	 * threadlocal class to check for reentry
	 * 
	 * @author hhuynh
	 * 
	 */
	private static class VisitLog extends ThreadLocal<Boolean> {
		@Override
		protected Boolean initialValue() {
			return false;
		}

		public boolean hasVisited() {
			return get();
		}

		public void markAsVisited() {
			set(true);
		}

		public void clear() {
			super.remove();
		}
	}

}
